Yesterday we talked about if statements and for loops. Today let's add in "while" loops.

A while loop continues to do something while some criterion is still true. This gives you the advantage of not being tied down to a fixed number of steps beforehand. Good! This is especially useful for things like minimization. You know you're doing some process to find the minimium, but you don't necessarily know when you'll get there. This is important in astrophysics for things like Kepler's equation. It has a potential disadvantage though---you may never break the loop!


In [2]:
g = 0
while g < 10:
    print g
    g += 1


0
1
2
3
4
5
6
7
8
9

In [ ]:
g = 10
while g != 5:
    g -= 0.000001
print g

In [6]:
g = 10
while abs(g - 5) > 0.001:
    g -= 0.001
print g


5.0
Breakout!

Update your FizzBuzz code to use a while loop instead of a for loop.

Tuples and lists.

Tuples are immutable ordered arrays. Lists are mutuable.

Also: dictionaries. A mapping from keys to values.

First, let's talk about tuples. These are defined using parentheses.


In [7]:
t = (12, -1)
print(type(t))


<type 'tuple'>

In [8]:
print(isinstance(t,tuple))
print(len(t))


True
2

In [9]:
t = (12, "monty", True, -1.23e6)
print(t[1])


monty

In [10]:
t[-2:]  # get the last two elements, return as a tuple


Out[10]:
(True, -1230000.0)

In [11]:
x = ()
type(x), len(x)


Out[11]:
(tuple, 0)

You can't modify a tuple. You can concatenate multiple tuples together.


In [12]:
y = t[0:2] + (False,) + t[3:]
y


Out[12]:
(12, 'monty', False, -1230000.0)

Lists have lots of the same functionality as tuples, but can be modified. They use square brackets. (You saw one yesterday).


In [13]:
v = ["eggs", "I", -1, ("monty","python"), [-1.2,-3.5]]
len(v)


Out[13]:
5

In [14]:
v[0] ="green egg"
v[1] += " love it."
v[-1]


Out[14]:
[-1.2, -3.5]

In [15]:
v = [1,2,3]
v.append(4)
v.append([-5])
v


Out[15]:
[1, 2, 3, 4, [-5]]

Note: lists can be considered objects. Objects are collections of data and associated methods. In the case of a list, append is a method: it is a function associated with the object.


In [16]:
w = ['elderberries', 'eggs']

In [17]:
v.extend(w)
v


Out[17]:
[1, 2, 3, 4, [-5], 'elderberries', 'eggs']

In [18]:
v.pop()


Out[18]:
'eggs'

In [19]:
v


Out[19]:
[1, 2, 3, 4, [-5], 'elderberries']

In [20]:
v.pop(0) ## pop the first element


Out[20]:
1

In [21]:
v


Out[21]:
[2, 3, 4, [-5], 'elderberries']

Useful list methods:

.append(): adds a new element

.extend(): concatenates a list/element

.pop(): remove an element


In [25]:
help(v.index(2))


Help on int object:

class int(object)
 |  int(x=0) -> int or long
 |  int(x, base=10) -> int or long
 |  
 |  Convert a number or string to an integer, or return 0 if no arguments
 |  are given.  If x is floating point, the conversion truncates towards zero.
 |  If x is outside the integer range, the function returns a long instead.
 |  
 |  If x is not a number or if base is given, then x must be a string or
 |  Unicode object representing an integer literal in the given base.  The
 |  literal can be preceded by '+' or '-' and be surrounded by whitespace.
 |  The base defaults to 10.  Valid bases are 0 and 2-36.  Base 0 means to
 |  interpret the base from the string as an integer literal.
 |  >>> int('0b100', base=0)
 |  4
 |  
 |  Methods defined here:
 |  
 |  __abs__(...)
 |      x.__abs__() <==> abs(x)
 |  
 |  __add__(...)
 |      x.__add__(y) <==> x+y
 |  
 |  __and__(...)
 |      x.__and__(y) <==> x&y
 |  
 |  __cmp__(...)
 |      x.__cmp__(y) <==> cmp(x,y)
 |  
 |  __coerce__(...)
 |      x.__coerce__(y) <==> coerce(x, y)
 |  
 |  __div__(...)
 |      x.__div__(y) <==> x/y
 |  
 |  __divmod__(...)
 |      x.__divmod__(y) <==> divmod(x, y)
 |  
 |  __float__(...)
 |      x.__float__() <==> float(x)
 |  
 |  __floordiv__(...)
 |      x.__floordiv__(y) <==> x//y
 |  
 |  __format__(...)
 |  
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |  
 |  __getnewargs__(...)
 |  
 |  __hash__(...)
 |      x.__hash__() <==> hash(x)
 |  
 |  __hex__(...)
 |      x.__hex__() <==> hex(x)
 |  
 |  __index__(...)
 |      x[y:z] <==> x[y.__index__():z.__index__()]
 |  
 |  __int__(...)
 |      x.__int__() <==> int(x)
 |  
 |  __invert__(...)
 |      x.__invert__() <==> ~x
 |  
 |  __long__(...)
 |      x.__long__() <==> long(x)
 |  
 |  __lshift__(...)
 |      x.__lshift__(y) <==> x<<y
 |  
 |  __mod__(...)
 |      x.__mod__(y) <==> x%y
 |  
 |  __mul__(...)
 |      x.__mul__(y) <==> x*y
 |  
 |  __neg__(...)
 |      x.__neg__() <==> -x
 |  
 |  __nonzero__(...)
 |      x.__nonzero__() <==> x != 0
 |  
 |  __oct__(...)
 |      x.__oct__() <==> oct(x)
 |  
 |  __or__(...)
 |      x.__or__(y) <==> x|y
 |  
 |  __pos__(...)
 |      x.__pos__() <==> +x
 |  
 |  __pow__(...)
 |      x.__pow__(y[, z]) <==> pow(x, y[, z])
 |  
 |  __radd__(...)
 |      x.__radd__(y) <==> y+x
 |  
 |  __rand__(...)
 |      x.__rand__(y) <==> y&x
 |  
 |  __rdiv__(...)
 |      x.__rdiv__(y) <==> y/x
 |  
 |  __rdivmod__(...)
 |      x.__rdivmod__(y) <==> divmod(y, x)
 |  
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |  
 |  __rfloordiv__(...)
 |      x.__rfloordiv__(y) <==> y//x
 |  
 |  __rlshift__(...)
 |      x.__rlshift__(y) <==> y<<x
 |  
 |  __rmod__(...)
 |      x.__rmod__(y) <==> y%x
 |  
 |  __rmul__(...)
 |      x.__rmul__(y) <==> y*x
 |  
 |  __ror__(...)
 |      x.__ror__(y) <==> y|x
 |  
 |  __rpow__(...)
 |      y.__rpow__(x[, z]) <==> pow(x, y[, z])
 |  
 |  __rrshift__(...)
 |      x.__rrshift__(y) <==> y>>x
 |  
 |  __rshift__(...)
 |      x.__rshift__(y) <==> x>>y
 |  
 |  __rsub__(...)
 |      x.__rsub__(y) <==> y-x
 |  
 |  __rtruediv__(...)
 |      x.__rtruediv__(y) <==> y/x
 |  
 |  __rxor__(...)
 |      x.__rxor__(y) <==> y^x
 |  
 |  __str__(...)
 |      x.__str__() <==> str(x)
 |  
 |  __sub__(...)
 |      x.__sub__(y) <==> x-y
 |  
 |  __truediv__(...)
 |      x.__truediv__(y) <==> x/y
 |  
 |  __trunc__(...)
 |      Truncating an Integral returns itself.
 |  
 |  __xor__(...)
 |      x.__xor__(y) <==> x^y
 |  
 |  bit_length(...)
 |      int.bit_length() -> int
 |      
 |      Number of bits necessary to represent self in binary.
 |      >>> bin(37)
 |      '0b100101'
 |      >>> (37).bit_length()
 |      6
 |  
 |  conjugate(...)
 |      Returns self, the complex conjugate of any int.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  denominator
 |      the denominator of a rational number in lowest terms
 |  
 |  imag
 |      the imaginary part of a complex number
 |  
 |  numerator
 |      the numerator of a rational number in lowest terms
 |  
 |  real
 |      the real part of a complex number
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T


In [26]:
v.index?

Next, dictionaries! There are multiple ways to make a dictionary. They are defined with curly braces.


In [27]:
# number 1... curly braces & colons
d = {"favorite cat": None,
     "favorite spam": "all"}
d


Out[27]:
{'favorite cat': None, 'favorite spam': 'all'}

In [28]:
# number 3 ... just start filling in items/keys
d = {}  # empty dictionary
d['cat'] = 'dog'
d['one'] = 1
d['two'] = 2
d


Out[28]:
{'cat': 'dog', 'one': 1, 'two': 2}

In [29]:
# number 4... start with a list of tuples
mylist = [("cat","dog"), ("one",1), ("two",2)]
dict(mylist)


Out[29]:
{'cat': 'dog', 'one': 1, 'two': 2}

order doesn't matter!


In [30]:
d[0]


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-30-17371c6688f6> in <module>()
----> 1 d[0]

KeyError: 0

Lists: an alternative to for loops! For loops are slow in python and busy* ones should be avoided

Especially nested for loops, they can be trouble. Of course, if it would take more time to rewrite the code than the time you would gain by rewriting it, then that's no good either....


In [46]:
L = []

for num in range(100):
    if (num % 7 == 0) or (num % 11 == 0):
        L.append(num)
print(len(L))


23

In [47]:
L = [num for num in range(100)\
     if (num % 7 == 0) or (num % 11 == 0)]

print(len(L))


23

In [48]:
# Let's find all the near-earth Asteroids with 0.8 < a < 1.2 and e < 0.5.
# Each element is (name, semi-major axis (AU), eccentricity, orbit class)
# source: http://ssd.jpl.nasa.gov/sbdb_query.cgi

Asteroids = [('Eros', 1.457916888347732, 0.2226769029627053, 'AMO'),
             ('Albert', 2.629584157344544, 0.551788195302116, 'AMO'),
             ('Alinda', 2.477642943521562, 0.5675993715753302, 'AMO'),
             ('Ganymed', 2.662242764279804, 0.5339300994578989, 'AMO'),
             ('Amor', 1.918987277620309, 0.4354863345648127, 'AMO'),
             ('Icarus', 1.077941311539208, 0.826950446001521, 'APO'),
             ('Betulia', 2.196489260519891, 0.4876246891992282, 'AMO'),
             ('Geographos', 1.245477192797457, 0.3355407124897842, 'APO'),
             ('Ivar', 1.862724540418448, 0.3968541470639658, 'AMO'),
             ('Toro', 1.367247622946547, 0.4358829575017499, 'APO'),
             ('Apollo', 1.470694262588244, 0.5598306817483757, 'APO'),
             ('Antinous', 2.258479598510079, 0.6070051516585434, 'APO'),
             ('Daedalus', 1.460912865705988, 0.6144629118218898, 'APO'),
             ('Cerberus', 1.079965807367047, 0.4668134997419173, 'APO'),
             ('Sisyphus', 1.893726635847921, 0.5383319204425762, 'APO'),
             ('Quetzalcoatl', 2.544270656955212, 0.5704591861565643, 'AMO'),
             ('Boreas', 2.271958775354725, 0.4499332278634067, 'AMO'),
             ('Cuyo', 2.150453953345012, 0.5041719257675564, 'AMO'),
             ('Anteros', 1.430262719980132, 0.2558054402785934, 'AMO'),
             ('Tezcatlipoca', 1.709753263222791, 0.3647772103513082, 'AMO'),
             ('Midas', 1.775954494579457, 0.6503697243919138, 'APO'),
             ('Baboquivari', 2.646202507670927, 0.5295611095751231, 'AMO'),
             ('Anza', 2.26415089613359, 0.5371603112900858, 'AMO'),
             ('Aten', 0.9668828078092987, 0.1827831025175614, 'ATE'),
             ('Bacchus', 1.078135348117527, 0.3495569270441645, 'APO'),
             ('Ra-Shalom', 0.8320425524852308, 0.4364726062545577, 'ATE'),
             ('Adonis', 1.874315684524321, 0.763949321566, 'APO'),
             ('Tantalus', 1.289997492877751, 0.2990853014998932, 'APO'),
             ('Aristaeus', 1.599511990737142, 0.5030618532252225, 'APO'),
             ('Oljato', 2.172056090036035, 0.7125729402616418, 'APO'),
             ('Pele', 2.291471988746353, 0.5115484924883255, 'AMO'),
             ('Hephaistos', 2.159619960333728, 0.8374146846143349, 'APO'),
             ('Orthos', 2.404988778495748, 0.6569133796135244, 'APO'),
             ('Hathor', 0.8442121506103012, 0.4498204013480316, 'ATE'),
             ('Beltrovata', 2.104690977122337, 0.413731105995413, 'AMO'),
             ('Seneca', 2.516402574514213, 0.5708728441169761, 'AMO'),
             ('Krok', 2.152545170235639, 0.4478259793515817, 'AMO'),
             ('Eger', 1.404478323548423, 0.3542971360331806, 'APO'),
             ('Florence', 1.768227407864309, 0.4227761019048867, 'AMO'),
             ('Nefertiti', 1.574493139339916, 0.283902719273878, 'AMO'),
             ('Phaethon', 1.271195939723604, 0.8898716672181355, 'APO'),
             ('Ul', 2.102493486378346, 0.3951143067760007, 'AMO'),
             ('Seleucus', 2.033331705805067, 0.4559159977082651, 'AMO'),
             ('McAuliffe', 1.878722427225527, 0.3691521497610656, 'AMO'),
             ('Syrinx', 2.469752836845105, 0.7441934504192601, 'APO'),
             ('Orpheus', 1.209727780883745, 0.3229034563257626, 'APO'),
             ('Khufu', 0.989473784873371, 0.468479627898914, 'ATE'),
             ('Verenia', 2.093231870619781, 0.4865133359612604, 'AMO'),
             ('"Don Quixote"', 4.221712367193639, 0.7130894892477316, 'AMO'),
             ('Mera', 1.644476057737928, 0.3201425983025733, 'AMO')]

orbit_class = {'AMO':'Amor', 'APO':'Apollo', 'ATE':'Aten'}

In [49]:
# first we'll build the list using loops.
L = []
for data in Asteroids:
    name, a, e, t = data
    if abs(a - 1) < 0.2 and e < 0.5:
        L.append(name)
print(L)


['Cerberus', 'Aten', 'Bacchus', 'Ra-Shalom', 'Hathor', 'Khufu']
Breakout time!

1) Do the same thing, with a list comprehension instead of a for loop.

2) Create a dictionary using a list comprehension so that "print(D['Eros'])" returns (1.457916888347732, 0.2226769029627053, 'AMO')

3) Challenge set:

Using the above Asteroid list,

print the list sorted in alphabetical order by asteroid name (hint: how does sorting handle a list of tuples?) print the list sorted by semi-major axis print the list sorted by name, but replace the class code with the class name

The output should be formatted like this:

Asteroid name a (AU) e class ----------------------------------------- Eros 1.4578 0.2226 Amor Albert 2.6292 0.5518 Amor . . .

Bonus points if you can get the columns to line up nicely!

Tomorrow: Functions, modules, and writing beautiful code.

In [ ]: